import * as acorn from 'acorn'
export default class Explain {
  constructor(options) {
    this.options = options
    this.global = {
      console,
      Math,
      ...options
    }
    this.vars = {} //局部变量
  }
  parse(input) {
    let node = acorn.parse(input, { ecmaVersion: 2020 })
    console.log(node, 'root')
    this.explain(node)
  }
  explain(input) {
    let object, property, key, value
    switch (input.type) {
      case 'Program':
        input.body.forEach((res) => {
          this.explain(res)
        })
        break
      case 'ExpressionStatement':
        let { expression } = input
        return this.explain(expression)
        break
      case 'CallExpression':
        let { arguments: args, callee } = input
        let args_array = []
        args.forEach((res) => {
          args_array.push(this.explain(res))
        })
        let callee_explain = this.explain(callee)
        if (typeof callee_explain == 'string') {
          if (this.vars[callee_explain]) {
            return this.vars[callee_explain].apply(this.vars[callee_explain], args_array)
          } else {
            return this.global[callee_explain].apply(callee_explain, args_array)
          }
        } else if (callee_explain.property) {
          object = callee_explain.object
          property = callee_explain.property
          if (typeof object == 'object') {
            return object[property].apply(object, args_array)
          } else {
            return this.global[callee_explain.object][callee_explain.property].apply(null, args_array)
          }
        } else if (typeof callee_explain == 'function') {
          return callee_explain.apply(null, args_array)
        }
        break
      case 'NewExpression':
        return (() => {
          let args = input.arguments.map((res) => {
            return this.explain(res)
          })
          return this.explain(input.callee).apply({}, args)
        })()
        break
      case 'AssignmentExpression':
        return (() => {
          let { left, operator, right } = input
          left = this.explain(left)
          right = this.explain(right)
          if (operator == '=') {
            left.object[left.property] = right
          }
        })()
        break
      case 'ArrowFunctionExpression':
        return (() => {
          let { params } = input
          let params_array = []
          params_array = params.map((res) => {
            return this.explain(res)
          })
          return this.explain(input.body)(params_array)
        })()
        break
      case 'FunctionExpression':
        return (() => {
          let { params } = input
          let params_array = []
          params_array = params.map((res) => {
            return this.explain(res)
          })
          return this.explain(input.body)(params_array)
        })()
        break
      case 'FunctionDeclaration':
        return (() => {
          let { params, id, body } = input
          let fun_name = this.explain(id)
          let params_array = []
          params_array = params.map((res) => {
            return this.explain(res)
          })
          this.vars[fun_name] = this.explain(body)(params_array)
        })()
        break
      case 'BlockStatement':
        let that = this
        return function(vars) {
          return function() {
            var args = arguments
            let obj = {}
            vars.forEach((res, k) => {
              obj[res] = arguments[k]
            })
            let ep = new Explain({
              ...that.global,
              context: this
            })
            ep.vars = obj
            let result
            input.body.forEach((res) => {
              if (res.type == 'ReturnStatement') {
                result = ep.explain(res)
              } else {
                ep.explain(res)
              }
            })
            return result
          }
        }
        break
      case 'ThisExpression':
        return this.global.context
        break
      case 'VariableDeclaration':
        let { kind, declarations } = input
        declarations.forEach((res) => {
          this.explain(res)
        })
        break
      case 'VariableDeclarator':
        return (() => {
          let id = this.explain(input.id)
          let init = this.explain(input.init)
          this.vars[id] = init
        })()
        break
      case 'ReturnStatement':
        return this.explain(input.argument)
        break
      case 'BinaryExpression':
        return (() => {
          let { left, operator, right } = input
          left = this.explain(left)
          right = this.explain(right)
          switch (operator) {
            case '+':
              return left + right
              break
            case '-':
              return left - right
              break
            case '*':
              return left * right
              break
            case '/':
              return left / right
              break
          }
          return {
            left,
            operator,
            right
          }
        })()
        break
      case 'MemberExpression':
        object = input.object
        property = input.property
        let object_name = this.explain(object)
        let property_name = this.explain(property)
        return {
          object: object_name,
          property: property_name
        }
        break
      case 'ObjectExpression':
        object = {}
        input.properties.forEach((res) => {
          let { key, value } = this.explain(res)
          object[key] = value
        })
        return object
        break
      case 'Property':
        key = this.explain(input.key)
        value = this.explain(input.value)
        return {
          key,
          value
        }
        break
      case 'Identifier':
        return this.vars[input.name] || this.global[input.name] || input.name
        break
      case 'Literal':
        return input.value
        break
      case 'TemplateLiteral':
        let { quasis } = input
        let quasis_array = []
        quasis.forEach((res) => {
          quasis_array.push(this.explain(res))
        })
        return quasis_array[0]
        break
      case 'TemplateElement':
        value = input.value.cooked
        return value
        break
      default:
        break
    }
  }
}
